/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.rpc.api.serialize;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.iec.edp.caf.commons.core.SerializerFactory;
import io.iec.edp.caf.commons.core.enums.SerializeType;
import io.iec.edp.caf.commons.exception.CAFRuntimeException;

import io.iec.edp.caf.commons.exception.entity.DefaultExceptionProperties;
import io.iec.edp.caf.commons.exception.entity.ExceptionErrorCode;
import io.iec.edp.caf.commons.exception.ExceptionLevel;
import io.iec.edp.caf.commons.utils.InvokeService;
import io.iec.edp.caf.rpc.api.annotation.GspParamSerializeType;
import io.iec.edp.caf.rpc.api.annotation.RpcParam;

import io.iec.edp.caf.rpc.api.common.GspSerializeType;
import io.iec.edp.caf.rpc.api.entity.RpcParamDefinition;
import io.iec.edp.caf.rpc.api.entity.RpcReturnValueDefinition;
import io.iec.edp.caf.rpc.api.entity.RpcServiceMethodDefinition;
import io.iec.edp.caf.rpc.api.utils.RpcAppContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.DefaultParameterNameDiscoverer;

import java.lang.reflect.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Rpc相关序列化工具
 *
 * @author guowenchang
 */
@Slf4j
public class RpcSerializeUtil {

    private static ConcurrentHashMap<String, RpcCustomSerializer> customSerializersMap;

    //参数序列化 默认按JSON方式序列化
    private static LinkedHashMap<String, String> serializeParameter(HashMap<String, Object> parameters) {
        LinkedHashMap<String, String> stringDict = new LinkedHashMap<>();
        for (Map.Entry<String, Object> entry : parameters.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            stringDict.put(key, SerializerFactory.getSerializer(SerializeType.Json).serializeToString(value));
        }

        return stringDict;
    }

    //参数序列化 按定义序列化
    public static LinkedHashMap<String, String> serializeParameter(HashMap<String, Object> parameters,
                                                                   RpcServiceMethodDefinition rpcServiceMethodDefinition) {
        if(rpcServiceMethodDefinition==null){
            return serializeParameter(parameters);
        }

        List<RpcParamDefinition> paramDefinitions = rpcServiceMethodDefinition.getParameters();

        //构建definitionMap
        Map<String, RpcParamDefinition> paramDefinitionMap = new HashMap<>();
        for (RpcParamDefinition paramDefinition : paramDefinitions) {
            paramDefinitionMap.put(paramDefinition.getName(), paramDefinition);
        }

        //遍历并序列化
        LinkedHashMap<String, String> stringDict = new LinkedHashMap<>();
        for (Map.Entry<String, Object> entry : parameters.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            RpcParamDefinition paramNow = paramDefinitionMap.get(key);
            String valueStr;
            if (paramNow == null || paramNow.getSerializeType() == GspSerializeType.Json) {
                valueStr = SerializerFactory.getSerializer(SerializeType.Json).serializeToString(value);
            } else if (paramNow.getSerializeType() == GspSerializeType.Custom) {
                valueStr = getCustomSerializeClass(paramNow.getSerializer()).serialize(value);
            } else {
                throw new CAFRuntimeException(DefaultExceptionProperties.SERVICE_UNIT,
                        DefaultExceptionProperties.RESOURCE_FILE,
                        ExceptionErrorCode.serializeMethodNotSupport,
                        new String[]{paramNow.getSerializeType().toString()}, null, ExceptionLevel.Error, false);
            }

            stringDict.put(key, valueStr);
        }

        return stringDict;
    }

    //获取自定义序列化器
    private static RpcCustomSerializer getCustomSerializeClass(String implementClass) {
        //获取全局序列化器缓存
        if (customSerializersMap == null) {
            customSerializersMap = new ConcurrentHashMap<>();
        }

        //如果能找到 那么返回 找不到则反射调起来 加入缓存
        RpcCustomSerializer customSerializer = customSerializersMap.get(implementClass);
        if (customSerializer == null) {
            try {
                //注意此处需要传入线程持有的classLoader 否则对当前类的classLoader(LaunchedURLClassLoader)来说 SU中的Class将是不可见的
                customSerializer = (RpcCustomSerializer) InvokeService.getClassByName(implementClass).newInstance();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("Custom Serializer Not Found");
            }
            customSerializersMap.put(implementClass, customSerializer);
        }

        return customSerializer;
    }

    //序列化返回值
    public static String serializeReturnValue(Object returnValue, RpcReturnValueDefinition rpcReturnValueDefinition) {
        String returnStr = "";
        GspSerializeType gspSerializeType = rpcReturnValueDefinition == null ? GspSerializeType.Json : rpcReturnValueDefinition.getSerializeType();
        switch (gspSerializeType) {
            case Json:
                returnStr =SerializerFactory.getSerializer(SerializeType.Json).serializeToString(returnValue);
                break;
            case Custom:
                returnStr = getCustomSerializeClass(rpcReturnValueDefinition.getSerializer()).serialize(returnValue);
                break;
            default:
                throw new CAFRuntimeException(DefaultExceptionProperties.SERVICE_UNIT,
                        DefaultExceptionProperties.RESOURCE_FILE,
                        ExceptionErrorCode.serializeMethodNotSupport,
                        new String[]{rpcReturnValueDefinition.getSerializeType().toString()}, null);
        }

        return returnStr;
    }

    //反序列化返回值
    public static <T> T deSerializeReturnValue(io.iec.edp.caf.rpc.api.support.Type<T> type, Object returnStr, RpcServiceMethodDefinition rpcMethodDefinition) {
        GspSerializeType gspSerializeType;
        RpcReturnValueDefinition rpcReturnValueDefinition = new RpcReturnValueDefinition();
        if(rpcMethodDefinition ==null){
            gspSerializeType = GspSerializeType.Json;
        }else{
            rpcReturnValueDefinition = rpcMethodDefinition.getReturnInfo();
            gspSerializeType = rpcReturnValueDefinition == null ? GspSerializeType.Json : rpcReturnValueDefinition.getSerializeType();
        }

        T returnValue;
        switch (gspSerializeType) {
            case Protobuf:
                returnValue = null;
                break;
            case Json:
                returnValue = SerializerFactory.getSerializer(SerializeType.Json).deserialize((String)returnStr, type.getJavaType());
                break;
            case Custom:
                returnValue = getCustomSerializeClass(rpcReturnValueDefinition.getSerializer()).deserialize((String)returnStr, type.getRawType());
                break;
            default:
                throw new CAFRuntimeException(DefaultExceptionProperties.SERVICE_UNIT,
                        DefaultExceptionProperties.RESOURCE_FILE,
                        ExceptionErrorCode.serializeMethodNotSupport,
                        new String[]{rpcReturnValueDefinition.getSerializeType().toString()}, null);
        }

        return returnValue;
    }

    /**
     * deserialize service parameters
     * @param iType        service implement interface type
     * @param type         service type
     * @param paramDict    parameters map
     * @param methodName   service method name
     * @return deserialized parameters array
     */
    public static Object[] deSerializeParameter(Class iType, Class type, Map<String, Object> paramDict, String methodName) {
        ObjectMapper objectMapper = new ObjectMapper();

        //实例方法/接口方法
        Method method = RpcAppContextUtils.getMethodByName(type, methodName);
        Method iMethod = RpcAppContextUtils.getMethodByName(iType, methodName);

        //实例参数/接口参数
        Parameter[] parameters = method.getParameters();
        Parameter[] iParameters = iMethod.getParameters();

        Object[] paramStrArray = new Object[parameters.length];

        //方法参数名集合
        DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
        String[] paramsTruthNames = discover.getParameterNames(method);
        //注解列表
        GspParamSerializeType[] annotations = iMethod.getDeclaredAnnotationsByType(GspParamSerializeType.class);

        //参数绑定与反序列化
        for (int i = 0; i < paramStrArray.length; i++) {
            Parameter parameter = parameters[i];
            Parameter iParameter = iParameters[i];


            //参数名称 这里判断是否有RpcParam注解 有的话从注解拿 没有的话从TruthName拿
            RpcParam rpcParam = iParameter.getDeclaredAnnotation(RpcParam.class);
            String paramName = rpcParam == null ? paramsTruthNames[i] : rpcParam.paramName();

            //参数值
            Object paramValue = paramDict.get(paramName);
            //if the parameter is response then do not serialize
            if(rpcParam!=null && rpcParam.streamParam()){
                paramStrArray[i] = paramValue;
                continue;
            }

            //todo 临时解决方案 兼容单参数调用 2011移除
            if ((paramValue == null) && (paramDict.size() == 1) && (paramStrArray.length == 1)) {
                paramValue = (String) paramDict.values().toArray()[0];
            }

            //参数类型
            Type paramType = parameter.getParameterizedType();

            //获取序列化方式
            GspSerializeType serializeType;
            String customClass;
            if (rpcParam != null) {
                //先从RpcParam取
                serializeType = rpcParam.paramSerializeType();
                customClass = rpcParam.customSerializeTypeRef();
            } else {
                //再从GspParamSerializeType取
                GspParamSerializeType gspParamSerializeType = getParamAnnotation(paramName, annotations);
                if (gspParamSerializeType == null) {
                    //都没有则为默认
                    //todo 这里可以在来一个总体开关，决定rpc总体走什么序列化。总体开关默认json
                    serializeType = GspSerializeType.Json;
                    customClass = "";
                } else {
                    serializeType = gspParamSerializeType.paramSerializeType();
                    customClass = gspParamSerializeType.customSerializeTypeRef();
                }

                log.warn("can not find RpcParam annotation in rpc interface type: " + iType.getName()+",please add");
            }

            //反序列化
            Object value;
            switch (serializeType) {
                case Protobuf:
                    value = null;
                    break;
                case Json:
                    JavaType javaType = objectMapper.constructType(paramType);
                    value = SerializerFactory.getSerializer(SerializeType.Json).deserialize((String)paramValue, javaType);
                    break;
                case Custom:
                    //获取参数的真实类型 并反序列化
                    Type tempType = paramType;
                    while (!(tempType instanceof Class)) {
                        if (tempType instanceof ParameterizedType) {
                            tempType = ((ParameterizedType) tempType).getRawType();
                        } else if (tempType instanceof GenericArrayType) {
                            tempType = ((GenericArrayType) tempType).getGenericComponentType();
                        } else {
                            throw new RuntimeException("unsupport Type: " + tempType.getTypeName());
                        }
                    }

                    Class clazz = (Class) tempType;
                    value = getCustomSerializeClass(customClass).deserialize((String) paramValue, clazz);
                    break;
                default:
                    throw new CAFRuntimeException(DefaultExceptionProperties.SERVICE_UNIT,
                            DefaultExceptionProperties.RESOURCE_FILE,
                            ExceptionErrorCode.serializeMethodNotSupport,
                            new String[]{serializeType.toString()}, null, ExceptionLevel.Error, false);
            }
            paramStrArray[i] = value;
        }

        return paramStrArray;
    }

    private static GspParamSerializeType getParamAnnotation(String name, GspParamSerializeType[] annotations) {
        for (GspParamSerializeType annotation : annotations) {
            if (name.equals(annotation.paramName())) {
                return annotation;
            }
        }

        return null;
    }
}
